1//////////////////////////////////////////////////////////////////////
2// LibFile: utility.scad
3// Functions for type checking, handling undefs, processing function arguments,
4// and testing.
5// Includes:
6// include <BOSL2/std.scad>
7// FileGroup: Data Management
8// FileSummary: Type checking, dealing with undefs, processing function args
9// FileFootnotes: STD=Included in std.scad
10//////////////////////////////////////////////////////////////////////
11
12
13
14// Section: Type Checking
15
16
17// Function: typeof()
18// Synopsis: Returns a string representing the type of the value.
19// Topics: Type Checking
20// See Also: is_type()
21// Usage:
22// typ = typeof(x);
23// Description:
24// Returns a string representing the type of the value. One of "undef", "boolean", "number", "nan", "string", "list", "range", "function" or "invalid".
25// Some malformed "ranges", like '[0:NAN:INF]' and '[0:"a":INF]', may be classified as "undef" or "invalid".
26// Arguments:
27// x = value whose type to check
28// Example:
29// typ = typeof(undef); // Returns: "undef"
30// typ = typeof(true); // Returns: "boolean"
31// typ = typeof(42); // Returns: "number"
32// typ = typeof(NAN); // Returns: "nan"
33// typ = typeof("foo"); // Returns: "string"
34// typ = typeof([3,4,5]); // Returns: "list"
35// typ = typeof([3:1:8]); // Returns: "range"
36// typ = typeof(function (x,y) x+y); // Returns: "function"
37function typeof(x) =
38 is_undef(x)? "undef" :
39 is_bool(x)? "boolean" :
40 is_num(x)? "number" :
41 is_nan(x)? "nan" :
42 is_string(x)? "string" :
43 is_list(x)? "list" :
44 is_range(x) ? "range" :
45 version_num()>20210000 && is_function(x) ? "function" :
46 "invalid";
47
48
49// Function: is_type()
50// Synopsis: Returns true if the type of 'x' is one of those in the list `types`.
51// Topics: Type Checking
52// See Also: typeof()
53// Usage:
54// bool = is_type(x, types);
55// Description:
56// Returns true if the type of the value `x` is one of those given as strings in the list `types`.
57// Valid types are "undef", "boolean", "number", "nan", "string", "list", "range", or "function".
58// Arguments:
59// x = The value to check the type of.
60// types = A list of types to check
61// Example:
62// is_str_or_list = is_type("foo", ["string","list"]); // Returns: true
63// is_str_or_list2 = is_type([1,2,3], ["string","list"]); // Returns: true
64// is_str_or_list3 = is_type(2, ["string","list"]); // Returns: false
65// is_str = is_type("foo", "string"); // Returns: true
66// is_str2 = is_type([3,4], "string"); // Returns: false
67// is_str3 = is_type(["foo"], "string"); // Returns: false
68// is_str4 = is_type(3, "string"); // Returns: false
69function is_type(x,types) =
70 is_list(types)? in_list(typeof(x),types) :
71 is_string(types)? typeof(x) == types :
72 assert(is_list(types)||is_string(types));
73
74
75// Function: is_def()
76// Synopsis: Returns true if `x` is not `undef`.
77// Topics: Type Checking
78// See Also: typeof(), is_type(), is_str()
79// Usage:
80// bool = is_def(x);
81// Description:
82// Returns true if `x` is not `undef`. False if `x==undef`.
83// Arguments:
84// x = value to check
85// Example:
86// bool = is_def(undef); // Returns: false
87// bool = is_def(false); // Returns: true
88// bool = is_def(42); // Returns: true
89// bool = is_def("foo"); // Returns: true
90function is_def(x) = !is_undef(x);
91
92
93// Function: is_str()
94// Synopsis: Returns true if the argument is a string.
95// Topics: Type Checking
96// See Also: typeof(), is_type(), is_int(), is_def(), is_int()
97// Usage:
98// bool = is_str(x);
99// Description:
100// Returns true if `x` is a string. A shortcut for `is_string()`.
101// Arguments:
102// x = value to check
103// Example:
104// bool = is_str(undef); // Returns: false
105// bool = is_str(false); // Returns: false
106// bool = is_str(42); // Returns: false
107// bool = is_str("foo"); // Returns: true
108function is_str(x) = is_string(x);
109
110
111// Function: is_int()
112// Alias: is_integer()
113// Synopsis: Returns true if the argument is an integer.
114// Topics: Type Checking
115// See Also: typeof(), is_type(), is_str(), is_def()
116// Usage:
117// bool = is_int(n);
118// bool = is_integer(n);
119// Description:
120// Returns true if the given value is an integer (it is a number and it rounds to itself).
121// Arguments:
122// n = value to check
123// Example:
124// bool = is_int(undef); // Returns: false
125// bool = is_int(false); // Returns: false
126// bool = is_int(42); // Returns: true
127// bool = is_int("foo"); // Returns: false
128function is_int(n) = is_finite(n) && n == round(n);
129function is_integer(n) = is_finite(n) && n == round(n);
130
131
132// Function: all_integer()
133// Synopsis: Returns true if all of the numbers in the argument are integers.
134// Topics: Type Checking
135// See also: is_int(), typeof(), is_type()
136// Usage:
137// bool = all_integer(x);
138// Description:
139// If given a number, returns true if the number is a finite integer.
140// If given an empty list, returns false. If given a non-empty list, returns
141// true if every item of the list is an integer. Otherwise, returns false.
142// Arguments:
143// x = The value to check.
144// Example:
145// b = all_integer(true); // Returns: false
146// b = all_integer("foo"); // Returns: false
147// b = all_integer(4); // Returns: true
148// b = all_integer(4.5); // Returns: false
149// b = all_integer([]); // Returns: false
150// b = all_integer([3,4,5]); // Returns: true
151// b = all_integer([3,4.2,5]); // Returns: false
152// b = all_integer([3,[4,7],5]); // Returns: false
153function all_integer(x) =
154 is_num(x)? is_int(x) :
155 is_list(x)? (x != [] && [for (xx=x) if(!is_int(xx)) 1] == []) :
156 false;
157
158
159// Function: is_nan()
160// Synopsis: Return true if the argument is "not a number".
161// Topics: Type Checking
162// See Also: typeof(), is_type(), is_str(), is_def(), is_int(), is_finite()
163// Usage:
164// bool = is_nan(x);
165// Description:
166// Returns true if a given value `x` is nan, a floating point value representing "not a number".
167// Arguments:
168// x = value to check
169// Example:
170// bool = is_nan(undef); // Returns: false
171// bool = is_nan(false); // Returns: false
172// bool = is_nan(42); // Returns: false
173// bool = is_nan("foo"); // Returns: false
174// bool = is_nan(NAN); // Returns: true
175function is_nan(x) = (x!=x);
176
177
178// Function: is_finite()
179// Synopsis: Returns true if the argument is a finite number.
180// Topics: Type Checking
181// See Also: typeof(), is_type(), is_str(), is_def(), is_int(), is_nan()
182// Usage:
183// bool = is_finite(x);
184// Description:
185// Returns true if a given value `x` is a finite number.
186// Arguments:
187// x = value to check
188// Example:
189// bool = is_finite(undef); // Returns: false
190// bool = is_finite(false); // Returns: false
191// bool = is_finite(42); // Returns: true
192// bool = is_finite("foo"); // Returns: false
193// bool = is_finite(NAN); // Returns: false
194// bool = is_finite(INF); // Returns: false
195// bool = is_finite(-INF); // Returns: false
196function is_finite(x) = is_num(x) && !is_nan(0*x);
197
198
199// Function: is_range()
200// Synopsis: Returns true if the argument is a range.
201// Topics: Type Checking
202// See Also: typeof(), is_type(), is_str(), is_def(), is_int()
203// Usage:
204// bool = is_range(x);
205// Description:
206// Returns true if its argument is a range
207// Arguments:
208// x = value to check
209// Example:
210// bool = is_range(undef); // Returns: false
211// bool = is_range(false); // Returns: false
212// bool = is_range(42); // Returns: false
213// bool = is_range([3,4,5]); // Returns: false
214// bool = is_range("foo"); // Returns: false
215// bool = is_range([3:5]); // Returns: true
216function is_range(x) = !is_list(x) && is_finite(x[0]) && is_finite(x[1]) && is_finite(x[2]) ;
217
218
219// Function: valid_range()
220// Synopsis: Returns true if the argument is a valid range.
221// Topics: Type Checking
222// See Also: typeof(), is_type(), is_str(), is_def(), is_int(), is_range()
223// Usage:
224// bool = valid_range(x);
225// Description:
226// Returns true if its argument is a valid range (deprecated ranges excluded).
227// Arguments:
228// x = value to check
229// Example:
230// bool = is_range(undef); // Returns: false
231// bool = is_range(false); // Returns: false
232// bool = is_range(42); // Returns: false
233// bool = is_range([3,4,5]); // Returns: false
234// bool = is_range("foo"); // Returns: false
235// bool = is_range([3:5]); // Returns: true
236// bool = is_range([3:1]); // Returns: false
237function valid_range(x) =
238 is_range(x)
239 && ( x[1]>0
240 ? x[0]<=x[2]
241 : ( x[1]<0 && x[0]>=x[2] ) );
242
243
244// Function: is_func()
245// Synopsis: Returns true if the argument is a function literal.
246// Topics: Type Checking, Function Literals
247// See also: is_type(), typeof()
248// Usage:
249// bool = is_func(x);
250// Description:
251// Returns true if OpenSCAD supports function literals, and the given item is one.
252// Arguments:
253// x = The value to check
254// Example:
255// f = function (a) a==2;
256// bool = is_func(f); // Returns: true
257function is_func(x) = version_num()>20210000 && is_function(x);
258
259
260// Function: is_consistent()
261// Synopsis: Returns true if the argument is a list with consistent structure and finite numerical data.
262// Topics: Type Checking, Testing
263// See Also: typeof(), is_type(), is_str(), is_def(), is_int(), is_range(), is_homogeneous()
264// Usage:
265// bool = is_consistent(list, [pattern]);
266// Description:
267// Tests whether input is a list of entries which all have the same list structure
268// and are filled with finite numerical data. You can optionally specify a required
269// list structure with the pattern argument.
270// It returns `true` for the empty list regardless the value of the `pattern`.
271// Arguments:
272// list = list to check
273// pattern = optional pattern required to match
274// Example:
275// is_consistent([3,4,5]); // Returns true
276// is_consistent([[3,4],[4,5],[6,7]]); // Returns true
277// is_consistent([[3,4,5],[3,4]]); // Returns false
278// is_consistent([[3,[3,4,[5]]], [5,[2,9,[9]]]]); // Returns true
279// is_consistent([[3,[3,4,[5]]], [5,[2,9,9]]]); // Returns false
280// is_consistent([3,4,5], 0); // Returns true
281// is_consistent([3,4,undef], 0); // Returns false
282// is_consistent([[3,4],[4,5]], [1,1]); // Returns true
283// is_consistent([[3,"a"],[4,true]], [1,undef]); // Returns true
284// is_consistent([[3,4], 6, [4,5]], [1,1]); // Returns false
285// is_consistent([[1,[3,4]], [4,[5,6]]], [1,[2,3]]); // Returns true
286// is_consistent([[1,[3,INF]], [4,[5,6]]], [1,[2,3]]); // Returns false
287// is_consistent([], [1,[2,3]]); // Returns true
288function is_consistent(list, pattern) =
289 is_list(list)
290 && (len(list)==0
291 || (let(pattern = is_undef(pattern) ? _list_pattern(list[0]): _list_pattern(pattern) )
292 []==[for(entry=0*list) if (entry != pattern) entry]));
293
294//Internal function
295//Creates a list with the same structure of `list` with each of its elements replaced by 0.
296function _list_pattern(list) =
297 is_list(list)
298 ? [for(entry=list) is_list(entry) ? _list_pattern(entry) : 0]
299 : 0;
300
301
302// Function: same_shape()
303// Synopsis: Returns true if the argument lists are numeric and of the same shape.
304// Topics: Type Checking, Testing
305// See Also: is_homogeneous(), is_consistent()
306// Usage:
307// bool = same_shape(a,b);
308// Description:
309// Tests whether the inputs `a` and `b` are both numeric and are the same shaped list.
310// Example:
311// same_shape([3,[4,5]],[7,[3,4]]); // Returns true
312// same_shape([3,4,5], [7,[3,4]]); // Returns false
313function same_shape(a,b) = is_def(b) && _list_pattern(a) == b*0;
314
315
316// Function: is_bool_list()
317// Synopsis: Returns true if the argument list contains only booleans.
318// Topics: Boolean Testing
319// See Also: is_homogeneous(), is_consistent()
320// Usage:
321// check = is_bool_list(list,[length])
322// Description:
323// Tests whether input is a list containing only booleans, and optionally checks its length.
324// Arguments:
325// list = list to test
326// length = if given, list must be this length
327function is_bool_list(list, length) =
328 is_list(list) && (is_undef(length) || len(list)==length) && []==[for(entry=list) if (!is_bool(entry)) 1];
329
330
331// Section: Boolean list testing
332
333// Function: any()
334// Synopsis: Returns true if any item in the argument list is true.
335// Topics: Type Checking
336// See Also: all(), num_true()
337// Usage:
338// bool = any(l);
339// bool = any(l, func); // Requires OpenSCAD 2021.01 or later.
340// Requirements:
341// Requires OpenSCAD 2021.01 or later to use the `func` argument.
342// Description:
343// Returns true if any item in list `l` evaluates as true.
344// If `func` is given then returns true if the function evaluates as true on any list entry.
345// Items that evaluate as true include nonempty lists, nonempty strings, and nonzero numbers.
346// Arguments:
347// l = The list to test for true items.
348// func = An optional function literal of signature (x), returning bool, to test each list item with.
349// Example:
350// any([0,false,undef]); // Returns false.
351// any([1,false,undef]); // Returns true.
352// any([1,5,true]); // Returns true.
353// any([[0,0], [0,0]]); // Returns true.
354// any([[0,0], [1,0]]); // Returns true.
355function any(l, func) =
356 assert(is_list(l), "The input is not a list." )
357 assert(func==undef || is_func(func))
358 is_func(func)
359 ? _any_func(l, func)
360 : _any_bool(l);
361
362function _any_func(l, func, i=0, out=false) =
363 i >= len(l) || out? out :
364 _any_func(l, func, i=i+1, out=out || func(l[i]));
365
366function _any_bool(l, i=0, out=false) =
367 i >= len(l) || out? out :
368 _any_bool(l, i=i+1, out=out || l[i]);
369
370
371// Function: all()
372// Synopsis: Returns true if all items in the argument list are true.
373// Topics: Type Checking
374// See Also: any(), num_true()
375// Usage:
376// bool = all(l);
377// bool = all(l, func); // Requires OpenSCAD 2021.01 or later.
378// Requirements:
379// Requires OpenSCAD 2021.01 or later to use the `func` argument.
380// Description:
381// Returns true if all items in list `l` evaluate as true.
382// If `func` is given then returns true if the function evaluates as true on all list etnries.
383// Items that evaluate as true include nonempty lists, nonempty strings, and nonzero numbers.
384// Arguments:
385// l = The list to test for true items.
386// func = An optional function literal of signature (x), returning bool, to test each list item with.
387// Example:
388// test1 = all([0,false,undef]); // Returns false.
389// test2 = all([1,false,undef]); // Returns false.
390// test3 = all([1,5,true]); // Returns true.
391// test4 = all([[0,0], [0,0]]); // Returns true.
392// test5 = all([[0,0], [1,0]]); // Returns true.
393// test6 = all([[1,1], [1,1]]); // Returns true.
394function all(l, func) =
395 assert(is_list(l), "The input is not a list.")
396 assert(func==undef || is_func(func))
397 is_func(func)
398 ? _all_func(l, func)
399 : _all_bool(l);
400
401function _all_func(l, func, i=0, out=true) =
402 i >= len(l) || !out? out :
403 _all_func(l, func, i=i+1, out=out && func(l[i]));
404
405function _all_bool(l, i=0, out=true) =
406 i >= len(l) || !out? out :
407 _all_bool(l, i=i+1, out=out && l[i]);
408
409
410// Function: num_true()
411// Synopsis: Returns the number of true entries in the arguemnt list.
412// Topics: Boolean Testing
413// See Also: any(), all()
414// Usage:
415// seq = num_true(l);
416// seq = num_true(l, func); // Requires OpenSCAD 2021.01 or later.
417// Requirements:
418// Requires OpenSCAD 2021.01 or later to use the `func=` argument.
419// Description:
420// Returns the number of items in `l` that evaluate as true. If `func` is given then counts
421// list entries where the function evaluates as true.
422// Items that evaluate as true include nonempty lists, nonempty strings, and nonzero numbers.
423// Arguments:
424// l = The list to test for true items.
425// func = An optional function literal of signature (x), returning bool, to test each list item with.
426// Example:
427// num1 = num_true([0,false,undef]); // Returns 0.
428// num2 = num_true([1,false,undef]); // Returns 1.
429// num3 = num_true([1,5,false]); // Returns 2.
430// num4 = num_true([1,5,true]); // Returns 3.
431// num5 = num_true([[0,0], [0,0]]); // Returns 2.
432// num6 = num_true([[], [1,0]]); // Returns 1.
433function num_true(l, func) =
434 assert(is_list(l))
435 assert(func==undef || is_func(func))
436 let(
437 true_list = is_def(func)? [for(entry=l) if (func(entry)) 1]
438 : [for(entry=l) if (entry) 1]
439 )
440 len(true_list);
441
442
443
444// Section: Handling `undef`s.
445
446
447// Function: default()
448// Synopsis: Returns a default value if the argument is 'undef', else returns the argument.
449// Topics: Undef Handling
450// See Also: first_defined(), one_defined(), num_defined()
451// Usage:
452// val = default(val, dflt);
453// Description:
454// Returns the value given as `v` if it is not `undef`.
455// Otherwise, returns the value of `dflt`.
456// Arguments:
457// v = Value to pass through if not `undef`.
458// dflt = Value to return if `v` *is* `undef`. Default: undef
459function default(v,dflt=undef) = is_undef(v)? dflt : v;
460
461
462// Function: first_defined()
463// Synopsis: Returns the first value in the argument list that is not 'undef'.
464// Topics: Undef Handling
465// See Also: default(), one_defined(), num_defined(), any_defined(), all_defined()
466// Usage:
467// val = first_defined(v, [recursive]);
468// Description:
469// Returns the first item in the list that is not `undef`.
470// If all items are `undef`, or list is empty, returns `undef`.
471// Arguments:
472// v = The list whose items are being checked.
473// recursive = If true, sublists are checked recursively for defined values. The first sublist that has a defined item is returned. Default: false
474// Example:
475// val = first_defined([undef,7,undef,true]); // Returns: 7
476function first_defined(v,recursive=false,_i=0) =
477 _i<len(v) && (
478 is_undef(v[_i]) || (
479 recursive &&
480 is_list(v[_i]) &&
481 is_undef(first_defined(v[_i],recursive=recursive))
482 )
483 )? first_defined(v,recursive=recursive,_i=_i+1) : v[_i];
484
485
486// Function: one_defined()
487// Synopsis: Returns the defined value in the argument list if only a single value is defined.
488// Topics: Undef Handling
489// See Also: default(), first_defined(), num_defined(), any_defined(), all_defined()
490// Usage:
491// val = one_defined(vals, names, [dflt])
492// Description:
493// Examines the input list `vals` and returns the entry which is not `undef`.
494// If more than one entry is not `undef` then an error is asserted, specifying
495// "Must define exactly one of" followed by the names in the `names` parameter.
496// If `dflt` is given, and all `vals` are `undef`, then the value in `dflt` is returned.
497// If `dflt` is *not* given, and all `vals` are `undef`, then an error is asserted.
498// Arguments:
499// vals = The values to return the first one which is not `undef`.
500// names = A string with comma-separated names for the arguments whose values are passed in `vals`.
501// dflt = If given, the value returned if all `vals` are `undef`.
502// Example:
503// length1 = one_defined([length,L,l], ["length","L","l"]);
504// length2 = one_defined([length,L,l], "length,L,l", dflt=1);
505
506function one_defined(vals, names, dflt=_UNDEF) =
507 let(
508 checkargs = is_list(names)? assert(len(vals) == len(names)) :
509 is_string(names)? let(
510 name_cnt = len([for (c=names) if (c==",") 1]) + 1
511 ) assert(len(vals) == name_cnt) :
512 assert(is_list(names) || is_string(names)) 0,
513 ok = num_defined(vals)==1 || (dflt!=_UNDEF && num_defined(vals)==0)
514 ) ok? default(first_defined(vals), dflt) :
515 let(
516 names = is_string(names) ? str_split(names,",") : names,
517 defd = [for (i=idx(vals)) if (is_def(vals[i])) names[i]],
518 msg = str(
519 "Must define ",
520 dflt==_UNDEF? "exactly" : "at most",
521 " one of ",
522 num_defined(vals) == 0 ? names : defd
523 )
524 ) assert(ok,msg);
525
526
527// Function: num_defined()
528// Synopsis: Returns the number of defined values in the the argument list.
529// Topics: Undef Handling
530// See Also: default(), first_defined(), one_defined(), any_defined(), all_defined()
531// Usage:
532// cnt = num_defined(v);
533// Description:
534// Counts how many items in list `v` are not `undef`.
535// Example:
536// cnt = num_defined([3,7,undef,2,undef,undef,1]); // Returns: 4
537function num_defined(v) =
538 len([for(vi=v) if(!is_undef(vi)) 1]);
539
540
541// Function: any_defined()
542// Synopsis: Returns true if any item in the argument list is not `undef`.
543// Topics: Undef Handling
544// See Also: default(), first_defined(), one_defined(), num_defined(), all_defined()
545// Usage:
546// bool = any_defined(v, [recursive]);
547// Description:
548// Returns true if any item in the given array is not `undef`.
549// Arguments:
550// v = The list whose items are being checked.
551// recursive = If true, any sublists are evaluated recursively. Default: false
552// Example:
553// bool = any_defined([undef,undef,undef]); // Returns: false
554// bool = any_defined([undef,42,undef]); // Returns: true
555// bool = any_defined([34,42,87]); // Returns: true
556// bool = any_defined([undef,undef,[undef]]); // Returns: true
557// bool = any_defined([undef,undef,[undef]],recursive=true); // Returns: false
558// bool = any_defined([undef,undef,[42]],recursive=true); // Returns: true
559function any_defined(v,recursive=false) =
560 first_defined(v,recursive=recursive) != undef;
561
562
563// Function: all_defined()
564// Synopsis: Returns true if all items in the given array are defined.
565// Topics: Undef Handling
566// See Also: default(), first_defined(), one_defined(), num_defined(), all_defined()
567// Usage:
568// bool = all_defined(v, [recursive]);
569// Description:
570// Returns true if all items in the given array are not `undef`.
571// Arguments:
572// v = The list whose items are being checked.
573// recursive = If true, any sublists are evaluated recursively. Default: false
574// Example:
575// bool = all_defined([undef,undef,undef]); // Returns: false
576// bool = all_defined([undef,42,undef]); // Returns: false
577// bool = all_defined([34,42,87]); // Returns: true
578// bool = all_defined([23,34,[undef]]); // Returns: true
579// bool = all_defined([23,34,[undef]],recursive=true); // Returns: false
580// bool = all_defined([23,34,[42]],recursive=true); // Returns: true
581function all_defined(v,recursive=false) =
582 []==[for (x=v) if(is_undef(x)||(recursive && is_list(x) && !all_defined(x,recursive))) 0 ];
583
584
585
586// Section: Undef Safe Arithmetic
587
588// Function: u_add()
589// Synopsis: Returns the sum of 2 numbers if both are defined, otherwise returns undef.
590// Topics: Undef Handling
591// See Also: u_sub(), u_mul(), u_div()
592// Usage:
593// x = u_add(a, b);
594// Description:
595// Adds `a` to `b`, returning the result, or undef if either value is `undef`.
596// This emulates the way undefs used to be handled in versions of OpenSCAD before 2020.
597// Arguments:
598// a = First value.
599// b = Second value.
600function u_add(a,b) = is_undef(a) || is_undef(b)? undef : a + b;
601
602
603// Function: u_sub()
604// Synopsis: Returns the difference of 2 numbers if both are defined, otherwise returns undef.
605// Topics: Undef Handling
606// See Also: u_add(), u_mul(), u_div()
607// Usage:
608// x = u_sub(a, b);
609// Description:
610// Subtracts `b` from `a`, returning the result, or undef if either value is `undef`.
611// This emulates the way undefs used to be handled in versions of OpenSCAD before 2020.
612// Arguments:
613// a = First value.
614// b = Second value.
615function u_sub(a,b) = is_undef(a) || is_undef(b)? undef : a - b;
616
617
618// Function: u_mul()
619// Synopsis: Returns the product of 2 numbers if both are defined, otherwise returns undef.
620// Topics: Undef Handling
621// See Also: u_add(), u_sub(), u_div()
622// Usage:
623// x = u_mul(a, b);
624// Description:
625// Multiplies `a` by `b`, returning the result, or undef if either value is `undef`.
626// This emulates the way undefs used to be handled in versions of OpenSCAD before 2020.
627// Arguments:
628// a = First value.
629// b = Second value.
630function u_mul(a,b) =
631 is_undef(a) || is_undef(b)? undef :
632 is_vector(a) && is_vector(b)? v_mul(a,b) :
633 a * b;
634
635
636// Function: u_div()
637// Synopsis: Returns the quotient of 2 numbers if both are defined, otherwise returns undef.
638// Topics: Undef Handling
639// See Also: u_add(), u_sub(), u_mul()
640// Usage:
641// x = u_div(a, b);
642// Description:
643// Divides `a` by `b`, returning the result, or undef if either value is `undef`.
644// This emulates the way undefs used to be handled in versions of OpenSCAD before 2020.
645// Arguments:
646// a = First value.
647// b = Second value.
648function u_div(a,b) =
649 is_undef(a) || is_undef(b)? undef :
650 is_vector(a) && is_vector(b)? v_div(a,b) :
651 a / b;
652
653
654
655
656// Section: Processing Arguments to Functions and Modules
657
658
659// Function: get_anchor()
660// Synopsis: Returns the correct anchor from `anchor` and `center`.
661// Topics: Argument Handling
662// See Also: get_radius()
663// Usage:
664// anchr = get_anchor(anchor,center,[uncentered],[dflt]);
665// Description:
666// Calculated the correct anchor from `anchor` and `center`. In order:
667// - If `center` is not `undef` and `center` evaluates as true, then `CENTER` (`[0,0,0]`) is returned.
668// - Otherwise, if `center` is not `undef` and `center` evaluates as false, then the value of `uncentered` is returned.
669// - Otherwise, if `anchor` is not `undef`, then the value of `anchor` is returned.
670// - Otherwise, the value of `dflt` is returned.
671// .
672// This ordering ensures that `center` will override `anchor`.
673// Arguments:
674// anchor = The anchor name or vector.
675// center = If not `undef`, this overrides the value of `anchor`.
676// uncentered = The value to return if `center` is not `undef` and evaluates as false. Default: BOTTOM
677// dflt = The default value to return if both `anchor` and `center` are `undef`. Default: `CENTER`
678// Example:
679// anchr1 = get_anchor(undef, undef, BOTTOM, TOP); // Returns: [0, 0, 1] (TOP)
680// anchr2 = get_anchor(RIGHT, undef, BOTTOM, TOP); // Returns: [1, 0, 0] (RIGHT)
681// anchr3 = get_anchor(undef, false, BOTTOM, TOP); // Returns: [0, 0,-1] (BOTTOM)
682// anchr4 = get_anchor(RIGHT, false, BOTTOM, TOP); // Returns: [0, 0,-1] (BOTTOM)
683// anchr5 = get_anchor(undef, true, BOTTOM, TOP); // Returns: [0, 0, 0] (CENTER)
684// anchr6 = get_anchor(RIGHT, true, BOTTOM, TOP); // Returns: [0, 0, 0] (CENTER)
685function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
686 !is_undef(center)? (center? CENTER : uncentered) :
687 !is_undef(anchor)? anchor :
688 dflt;
689
690
691// Function: get_radius()
692// Synopsis: Given various radii and diameters, returns the most specific radius.
693// Topics: Argument Handling
694// See Also: get_anchor()
695// Usage:
696// r = get_radius([r1=], [r2=], [r=], [d1=], [d2=], [d=], [dflt=]);
697// Description:
698// Given various radii and diameters, returns the most specific radius. If a diameter is most
699// specific, returns half its value, giving the radius. If no radii or diameters are defined,
700// returns the value of `dflt`. Value specificity order is `r1`, `r2`, `d1`, `d2`, `r`, `d`,
701// then `dflt`. Only one of `r1`, `r2`, `d1`, or `d2` can be defined at once, or else it errors
702// out, complaining about conflicting radius/diameter values.
703// Arguments:
704// ---
705// r1 = Most specific radius.
706// r2 = Second most specific radius.
707// r = Most general radius.
708// d1 = Most specific diameter.
709// d2 = Second most specific diameter.
710// d = Most general diameter.
711// dflt = Value to return if all other values given are `undef`.
712// Example:
713// r = get_radius(r1=undef, r=undef, dflt=undef); // Returns: undef
714// r = get_radius(r1=undef, r=undef, dflt=1); // Returns: 1
715// r = get_radius(r1=undef, r=6, dflt=1); // Returns: 6
716// r = get_radius(r1=7, r=6, dflt=1); // Returns: 7
717// r = get_radius(r1=undef, r2=8, r=6, dflt=1); // Returns: 8
718// r = get_radius(r1=undef, r2=8, d=6, dflt=1); // Returns: 8
719// r = get_radius(r1=undef, d=6, dflt=1); // Returns: 3
720// r = get_radius(d1=7, d=6, dflt=1); // Returns: 3.5
721// r = get_radius(d1=7, d2=8, d=6, dflt=1); // Returns: 3.5
722// r = get_radius(d1=undef, d2=8, d=6, dflt=1); // Returns: 4
723// r = get_radius(r1=8, d=6, dflt=1); // Returns: 8
724function get_radius(r1, r2, r, d1, d2, d, dflt) =
725 assert(num_defined([r1,d1,r2,d2])<2, "Conflicting or redundant radius/diameter arguments given.")
726 assert(num_defined([r,d])<2, "Conflicting or redundant radius/diameter arguments given.")
727 let(
728 rad = !is_undef(r1) ? r1
729 : !is_undef(d1) ? d1/2
730 : !is_undef(r2) ? r2
731 : !is_undef(d2) ? d2/2
732 : !is_undef(r) ? r
733 : !is_undef(d) ? d/2
734 : dflt
735 )
736 assert(is_undef(dflt) || is_finite(rad) || is_vector(rad), "Invalid radius." )
737 rad;
738
739
740// Function: scalar_vec3()
741// Synopsis: Expands a scalar or a list with length less than 3 to a length 3 vector.
742// Topics: Argument Handling
743// See Also: get_anchor(), get_radius(), force_list()
744// Usage:
745// vec = scalar_vec3(v, [dflt]);
746// Description:
747// This is expands a scalar or a list with length less than 3 to a length 3 vector in the
748// same way that OpenSCAD expands short vectors in some contexts, e.g. cube(10) or rotate([45,90]).
749// If `v` is a scalar, and `dflt==undef`, returns `[v, v, v]`.
750// If `v` is a scalar, and `dflt!=undef`, returns `[v, dflt, dflt]`.
751// If `v` is a vector and dflt is defined, returns the first 3 items, with any missing values replaced by `dflt`.
752// If `v` is a vector and dflt is undef, returns the first 3 items, with any missing values replaced by 0.
753// If `v` is `undef`, returns `undef`.
754// Arguments:
755// v = Value to return vector from.
756// dflt = Default value to set empty vector parts from.
757// Example:
758// vec = scalar_vec3(undef); // Returns: undef
759// vec = scalar_vec3(10); // Returns: [10,10,10]
760// vec = scalar_vec3(10,1); // Returns: [10,1,1]
761// vec = scalar_vec3([10,10],1); // Returns: [10,10,1]
762// vec = scalar_vec3([10,10]); // Returns: [10,10,0]
763// vec = scalar_vec3([10]); // Returns: [10,0,0]
764function scalar_vec3(v, dflt) =
765 is_undef(v)? undef :
766 is_list(v)? [for (i=[0:2]) default(v[i], default(dflt, 0))] :
767 !is_undef(dflt)? [v,dflt,dflt] : [v,v,v];
768
769// Function: segs()
770// Synopsis: Returns the number of sides for a circle given `$fn`, `$fa`, and `$fs`.
771// Topics: Geometry
772// See Also: circle(), cyl()
773// Usage:
774// sides = segs(r);
775// Description:
776// Calculate the standard number of sides OpenSCAD would give a circle based on `$fn`, `$fa`, and `$fs`.
777// Arguments:
778// r = Radius of circle to get the number of segments for.
779// Example:
780// $fn=12; sides=segs(10); // Returns: 12
781// $fa=2; $fs=3; sides=segs(10); // Returns: 21
782function segs(r) =
783 $fn>0? ($fn>3? $fn : 3) :
784 let( r = is_finite(r)? r : 0 )
785 ceil(max(5, min(360/$fa, abs(r)*2*PI/$fs)));
786
787
788// Module: no_children()
789// Synopsis: Assert that the calling module does not support children.
790// Topics: Error Checking
791// See Also: no_function(), no_module(), req_children()
792// Usage:
793// no_children($children);
794// Description:
795// Assert that the calling module does not support children. Prints an error message to this effect and fails if children are present,
796// as indicated by its argument.
797// Arguments:
798// $children = number of children the module has.
799// Example:
800// module foo() {
801// no_children($children);
802// }
803module no_children(count) {
804 assert($children==0, "Module no_children() does not support child modules");
805 if ($parent_modules>0) {
806 assert(count==0, str("Module ",parent_module(1),"() does not support child modules"));
807 }
808}
809
810
811// Module: req_children()
812// Synopsis: Assert that the calling module requires children.
813// Topics: Error Checking
814// See Also: no_function(), no_module()
815// Usage:
816// req_children($children);
817// Description:
818// Assert that the calling module requires children. Prints an error message and fails if no
819// children are present as indicated by its argument.
820// Arguments:
821// $children = number of children the module has.
822// Example:
823// module foo() {
824// req_children($children);
825// }
826module req_children(count) {
827 assert($children==0, "Module no_children() does not support child modules");
828 if ($parent_modules>0) {
829 assert(count>0, str("Module ",parent_module(1),"() requires children"));
830 }
831}
832
833
834// Function: no_function()
835// Synopsis: Assert that the argument exists only as a module and not as a function.
836// Topics: Error Checking
837// See Also: no_children(), no_module()
838// Usage:
839// dummy = no_function(name)
840// Description:
841// Asserts that the function, "name", only exists as a module.
842// Example:
843// x = no_function("foo");
844function no_function(name) =
845 assert(false,str("You called ",name,"() as a function, but it is available only as a module"));
846
847
848// Module: no_module()
849// Synopsis: Assert that the argument exists only as a function and not as a module.
850// Topics: Error Checking
851// See Also: no_children(), no_function()
852// Usage:
853// no_module();
854// Description:
855// Asserts that the called module exists only as a function.
856// Example:
857// module foo() { no_module(); }
858module no_module() {
859 assert(false, str("You called ",parent_module(1),"() as a module but it is available only as a function"));
860}
861
862
863// Module: deprecate()
864// Synopsis: Display a console note that a module is deprecated and suggest a replacement.
865// Topics: Error Checking
866// See Also: no_function(), no_module()
867// Usage:
868// deprecate(new_name);
869// Description:
870// Display info that the current module is deprecated and you should switch to a new name
871// Arguments:
872// new_name = name of the new module that replaces the old one
873module deprecate(new_name)
874{
875 echo(str("***** Module ",parent_module(1),"() has been replaced by ",new_name,"() and will be removed in a future version *****"));
876}
877
878
879// Section: Testing Helpers
880
881
882function _valstr(x) =
883 is_string(x)? str("\"",str_replace_char(x, "\"", "\\\""),"\"") :
884 is_list(x)? str("[",str_join([for (xx=x) _valstr(xx)],","),"]") :
885 is_num(x) && x==floor(x)? format_int(x) :
886 is_finite(x)? format_float(x,12) : x;
887
888
889// Module: assert_approx()
890// Synopsis: Assert that a value is approximately what was expected.
891// Topics: Error Checking, Debugging
892// See Also: no_children(), no_function(), no_module(), assert_equal()
893// Usage:
894// assert_approx(got, expected, [info]);
895// Description:
896// Tests if the value gotten is what was expected, plus or minus 1e-9. If not, then
897// the expected and received values are printed to the console and
898// an assertion is thrown to stop execution.
899// Returns false if both 'got' and 'expected' are 'nan'.
900// Arguments:
901// got = The value actually received.
902// expected = The value that was expected.
903// info = Extra info to print out to make the error clearer.
904// Example:
905// assert_approx(1/3, 0.333333333333333, str("number=",1,", denom=",3));
906module assert_approx(got, expected, info) {
907 no_children($children);
908 if (!approx(got, expected)) {
909 echo();
910 echo(str("EXPECT: ", _valstr(expected)));
911 echo(str("GOT : ", _valstr(got)));
912 if (same_shape(got, expected)) {
913 echo(str("DELTA : ", _valstr(got - expected)));
914 }
915 if (is_def(info)) {
916 echo(str("INFO : ", _valstr(info)));
917 }
918 assert(approx(got, expected));
919 }
920}
921
922
923// Module: assert_equal()
924// Synopsis: Assert that a value is expected.
925// See Also: no_children(), no_function(), no_module(), assert_approx()
926// Topics: Error Checking, Debugging
927// Usage:
928// assert_equal(got, expected, [info]);
929// Description:
930// Tests if the value gotten is what was expected. If not, then the expected and received values
931// are printed to the console and an assertion is thrown to stop execution.
932// Returns true if both 'got' and 'expected' are 'nan'.
933// Arguments:
934// got = The value actually received.
935// expected = The value that was expected.
936// info = Extra info to print out to make the error clearer.
937// Example:
938// assert_approx(3*9, 27, str("a=",3,", b=",9));
939module assert_equal(got, expected, info) {
940 no_children($children);
941 if (got != expected || (is_nan(got) && is_nan(expected))) {
942 echo();
943 echo(str("EXPECT: ", _valstr(expected)));
944 echo(str("GOT : ", _valstr(got)));
945 if (same_shape(got, expected)) {
946 echo(str("DELTA : ", _valstr(got - expected)));
947 }
948 if (is_def(info)) {
949 echo(str("INFO : ", _valstr(info)));
950 }
951 assert(got == expected);
952 }
953}
954
955
956// Module: shape_compare()
957// Synopsis: Compares two child shapes.
958// SynTags: Geom
959// Topics: Error Checking, Debugging, Testing
960// See Also: assert_approx(), assert_equal()
961// Usage:
962// shape_compare([eps]) {TEST_SHAPE; EXPECTED_SHAPE;}
963// Description:
964// Compares two child shapes, returning empty geometry if they are very nearly the same shape and size.
965// Returns the differential geometry if they are not quite the same shape and size.
966// Arguments:
967// eps = The surface of the two shapes must be within this size of each other. Default: 1/1024
968// Example:
969// $fn=36;
970// shape_compare() {
971// sphere(d=100);
972// rotate_extrude() right_half(planar=true) circle(d=100);
973// }
974module shape_compare(eps=1/1024) {
975 assert($children==2,"Must give exactly two children");
976 union() {
977 difference() {
978 children(0);
979 if (eps==0) {
980 children(1);
981 } else {
982 minkowski() {
983 children(1);
984 spheroid(r=eps, style="octa");
985 }
986 }
987 }
988 difference() {
989 children(1);
990 if (eps==0) {
991 children(0);
992 } else {
993 minkowski() {
994 children(0);
995 spheroid(r=eps, style="octa");
996 }
997 }
998 }
999 }
1000}
1001
1002
1003// Section: C-Style For Loop Helpers
1004// You can use a list comprehension with a C-style for loop to iteratively make a calculation.
1005// .
1006// The syntax is: `[for (INIT; CONDITION; NEXT) RETVAL]` where:
1007// - INIT is zero or more `let()` style assignments that are evaluated exactly one time, before the first loop.
1008// - CONDITION is an expression evaluated at the start of each loop. If true, continues with the loop.
1009// - RETVAL is an expression that returns a list item for each loop.
1010// - NEXT is one or more `let()` style assignments that is evaluated at the end of each loop.
1011// .
1012// Since the INIT phase is only run once, and the CONDITION and RETVAL expressions cannot update
1013// variables, that means that only the NEXT phase can be used for iterative calculations.
1014// Unfortunately, the NEXT phase runs *after* the RETVAL expression, which means that you need
1015// to run the loop one extra time to return the final value. This tends to make the loop code
1016// look rather ugly. The `looping()`, `loop_while()` and `loop_done()` functions
1017// can make this somewhat more legible.
1018// .
1019// ```openscad
1020// function flat_sum(l) = [
1021// for (
1022// i = 0,
1023// total = 0,
1024// state = 0;
1025//
1026// looping(state);
1027//
1028// state = loop_while(state, i < len(l)),
1029// total = total +
1030// loop_done(state) ? 0 :
1031// let( x = l[i] )
1032// is_list(x) ? flat_sum(x) : x,
1033// i = i + 1
1034// ) if (loop_done(state)) total;
1035// ].x;
1036// ```
1037
1038
1039// Function: looping()
1040// Synopsis: Returns true if the argument indicates the current C-style loop should continue.
1041// Topics: Iteration
1042// See Also: loop_while(), loop_done()
1043// Usage:
1044// bool = looping(state);
1045// Description:
1046// Returns true if the `state` value indicates the current loop should continue. This is useful
1047// when using C-style for loops to iteratively calculate a value. Used with `loop_while()` and
1048// `loop_done()`. See [Looping Helpers](section-looping-helpers) for an example.
1049// Arguments:
1050// state = The loop state value.
1051function looping(state) = state < 2;
1052
1053
1054// Function: loop_while()
1055// Synopsis: Returns true if both arguments indicate the current C-style loop should continue.
1056// Topics: Iteration
1057// See Also: looping(), loop_done()
1058// Usage:
1059// state = loop_while(state, continue);
1060// Description:
1061// Given the current `state`, and a boolean `continue` that indicates if the loop should still be
1062// continuing, returns the updated state value for the the next loop. This is useful when using
1063// C-style for loops to iteratively calculate a value. Used with `looping()` and `loop_done()`.
1064// See [Looping Helpers](section-looping-helpers) for an example.
1065// Arguments:
1066// state = The loop state value.
1067// continue = A boolean value indicating whether the current loop should progress.
1068function loop_while(state, continue) =
1069 state > 0 ? 2 :
1070 continue ? 0 : 1;
1071
1072
1073// Function: loop_done()
1074// Synopsis: Returns true if the argument indicates the current C-style loop is finishing.
1075// Topics: Iteration
1076// See Also: looping(), loop_while()
1077// Usage:
1078// bool = loop_done(state);
1079// Description:
1080// Returns true if the `state` value indicates the loop is finishing. This is useful when using
1081// C-style for loops to iteratively calculate a value. Used with `looping()` and `loop_while()`.
1082// See [Looping Helpers](#5-looping-helpers) for an example.
1083// Arguments:
1084// state = The loop state value.
1085function loop_done(state) = state > 0;
1086
1087
1088// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap